#ifndef TASK_MANAGE_H
#define TASK_MANAGE_H

#include <vector>
#include <list>
#include <memory>
#include <thread>
#include <mutex>
#include <optional>
#include "Task.h"
#include "TaskQueue.h"

namespace TaskManage
{
    template<typename T>
    class TaskManager
    {
    public:
        explicit TaskManager(int th = 1)
        {
            processingTask = std::vector<std::shared_ptr<Task<T>>>(th);
            threads = std::vector<std::thread *>(th);
        }

        void AppendTask(std::shared_ptr<TaskQueue<T>> taskQueue)
        {
            tQueueMutex.lock();
            tQueue.Append(taskQueue);
            tQueueMutex.unlock();
        }

        inline int GetRemainingSize()
        {
            tQueueMutex.lock();
            auto remaining = tQueue.GetSize();
            tQueueMutex.unlock();
            return remaining;
        }

        inline const std::list<std::tuple<int, std::optional<T>>> &GetResult() const
        {
            return resultList;
        }

        inline void Start()
        {
            for (int i = 0; i < threads.size(); i++)
                threads[i] = new std::thread(Worker(i, this));
        }

        inline void WaitForStop()
        {
            for (auto &i : threads)
            {
                if (i->joinable())
                    i->join();
                delete i;
            }
        }

    private:
        TaskQueue<T> tQueue;
        std::mutex tQueueMutex;
        std::vector<std::shared_ptr<Task<T>>> processingTask;
        std::vector<std::thread *> threads;
        std::list<std::tuple<int, std::optional<T>>> resultList;
        std::mutex resultListMutex;
    private:
        bool GetNextTask(int id)
        {
            tQueueMutex.lock();
            auto task = tQueue.GetNext();
            if (!task.has_value())
            {
                tQueueMutex.unlock();
                return false;
            }
            swap(processingTask[id], task.value());
            tQueueMutex.unlock();
            return true;
        }

        void SubmitResult(int id, std::optional<T> result)
        {
            resultListMutex.lock();
            resultList.emplace_back(std::tuple(id, result));
            resultListMutex.unlock();
        }

    private:
        class Worker
        {
        public:
            explicit Worker(int id, TaskManager<T> *tm) : id(id), tm(tm)
            {};

            void operator()()
            {
                while (true)
                {
                    if (!tm->GetNextTask(id))
                        break;
                    auto result = (*(tm->processingTask[id]))();
                    tm->SubmitResult(id, result);
                }
            }

        private:
            int id = 0;
            TaskManager<T> *tm;
        };

    };
}

#endif //TASK_MANAGE_H
